// 处理实例: 过程/挂起
const $MYSQL=require('mysql'), OPT={
	connectionLimit:2, supportBigNumbers:true,
	host:$.CFG.db.host, database:$.CFG.db.name,
	user:$.CFG.db.user, password:$.CFG.db.pass,
};
const $DBS = $MYSQL.createPool(OPT);
const $DBC = async function(){
	let DBC = $DBC.__ins;
	if(!DBC){
		DBC = $MYSQL.createConnection(OPT);
		return $DBC.__ins = DBC;
	}
	if(DBC instanceof Promise) return DBC;
	return $DBC.__ins = new Promise(DONE=>{
		DBC.ping(err=>{
			if(!err) return DONE(DBC);
			DBC=$MYSQL.createConnection(OPT);
			$DBC.__ins=DBC;  DONE(DBC);
		});
	});
};

$.srvHandle = class srvHandle {
	/**
	 * 构造函数
	 * @param opt json 实例选项
	 * 		mode: string 处理模式  hold,proc
	 * 			- 影响: log, end, __end__
	 * 			hold: 挂起模式  无结束(服务)
	 * 			proc: 过程模式  处理完后结束
	 * 		type: string 处理类型  自定, eg:
	 * 			main: [hold]主进程/线程  守护
	 * 			serv: [hold]子进程/线程  挂起
	 * 			bash: [proc]脚本执行  过程
	 * 			http: [proc]HTTP请求 处理过程
	 * 			....: 自定义
	 * 		log: boolean 是否存日志到文件  默认:true
	 * 			存储: {$.TEMP}/log/{date('/Y-m/d')}.log
	 * 		head: string|null [proc]日志头信息
	 * 			- proc:必需, hold:忽略,  替换:
	 * 			{KEY} => this.got(KEY)  KEY含{}
	 * 			acs:处理实例ID,  mem:处理开始内存
	 * 			date:开始时间, use:[proc]结束用时
	 */
	constructor(opt){
		const mem=process.memoryUsage().external, acs=$.id();
		const unix=new Date()*1, time=Math.floor(unix/1000);
		let date=$.date(time), {mode,type}=opt, log=opt.log??true;
		let head=mode!=='proc'?null:opt.head.replace('{acs}',acs)
			.replace('{mem}',mem).replace('{date}',date);
		let v = {logHead:head,logTemp:[],logPrev:unix,end:[]};
		Object.defineProperty(this, '__srvHandle', {value:v});
		v=Object.freeze({mem,acs,log,unix,time,date,mode,type});
		Object.defineProperty(this, '__srvHandle__', {value:v});
	}

	/**
	 * 过程执行  创建实例,并执行
	 * @param opt json 实例选项  constructor()参数opt
	 * @param back function 执行过程  内置捕获异常处理
	 */
	static run(opt, back){
		let K='$.srvHandle.run', H=new this.constructor(opt), R;
		try{ R=back(H); }catch(err){ H.log($.err(K,err,true)); }
		if(!(R instanceof Promise)) return H.__end__();
		return R.catch(err=>H.log($.err(K,err,true)))
			.then(()=>H.__end__());
	}

	/**
	 * 取得实例数据或内置
	 * @param k string|null 取得键  null:全部
	 * 		mode: 处理模式 constructor()参数opt.mode
	 * 		type: 处理类型 constructor()参数opt.type
	 * 		log: 是否存日志 constructor()参数opt.log
	 * 		mem: int 实例化时已使用的内存量
	 * 		acs: string 处理实例ID  $.id()
	 * 		unix: int 实例化时的时间戳(毫秒)
	 * 		time: int 实例化时的时间戳(毫秒)
	 * 		date: string 实例化时的时间  Y-m-d H:i:s
	 * @return json|mixed
	 */
	got(k=null){ const m=this.__srvHandle__; return k?m[k]:m; }
	/**
	 * 取得实例化时的时间戳
	 * @param u boolean 是否取毫秒
	 * @return int this.got(u?'unix':'time')
	 */
	now(u=false){ return this.got(u?'unix':'time'); }
	/**
	 * TODO
	 * @return string 处理实例ID
	 */
	acs(){ return this.__srvHandle__.acs; }
	/**
	 * 取得语言消息  TODO:...
	 * @param key string 语言编码
	 * @param args json|null 语言模板参数
	 * @return string
	 */
	msg(key, args=null){ return $.lang(key,args,['zh_cn']); }
	/**
	 * 日志:打印+文件
	 * @param mix mixed|null 日志内容
	 * @param type string|null 类型 日志/控制 可用:
	 * 		null: mix=$.err() ? 自动识别 : 否则DEBUG
	 * 		日志类型:  $._log的键
	 * 		控制类型:  仅mode=过程模式可用
	 * 			head: 取得头信息  save.head  替换'{use}'
	 * 			full: 取得已记录  head(替换'{use}')+main
	 * @return this
	 */
	log(mix, type=null){
		const isErrIns = mix instanceof $.err;
		if(isErrIns){ mix.log(type,this); return this; }
		const T=this.__srvHandle, M=this.__srvHandle__;
		// 控制类型
		if(type==='head'){
			const use = (T.endUnix||new Date()*1) - M.unix;
			return T.logHead.replace('{use}', $.dateUse(use));
		}
		if(type==='full'){
			const t=T.logTemp, h=this.log(null,'head');
			let m=t?t.join('\n'):''; return m?h+'\n'+m:h;
		}
		// 日志类型
		const mem=$.byte(process.memoryUsage().external-M.mem);
		let pre=$.log.hue(type||'DEBUG'), hold=M.mode!=='proc';
		if(hold){
			let ext=`\x1B[90m${$.date()} ${mem}\x1B[0m`, str;
			pre=`# ${pre} [${ext}]`; str=typeof mix!=='string';
			if(str) console.log('\n'+pre,mix); else{
				let log=$._log_echo?$._log_echo(mix,this):mix;
				console.log('\n'+pre+' '+log);
			}
		}
		else{
			let s=new Date()*1, u='+'+$.dateUse(s-T.logPrev);
			T.logPrev=s;  pre+=` [\x1B[90m${mem+u}\x1B[0m]`;
		}
		const txt = pre+' '+$.txt(mix).trim();
		hold ? this.#__logSave(txt) : T.logTemp.push(txt);
		return this;
	}
	/**
	 * 注册:结束后执行
	 * @param fn function 回调函数
	 * @return this
	 */
	end(fn){ this.__srvHandle.end.push(fn); return this; }
	// 结束后调用
	async __end__(){
		const k='$.srvHandle.__end__.', M=this.__srvHandle__;
		if(M.mode!=='proc') // 仅过程模式可用且必需调用
			return this.log($.err(k+'mode',M.mode,true));
		const endUnix=this.__srvHandle.endUnix=new Date()*1;
		if($.DEBUG) this.log('$.srvHandle.__end__.before');
		for(const b of this.__srvHandle.end)
			try{ await b(this); } catch(e){
				this.log($.err(k+'call',e,true));
			}
		if($.DEBUG){
			const use = $.dateUse(new Date()*1 - endUnix);
			this.log('$.srvHandle.__end__.after '+use);
		}
		if(!M.log) return;  const txt=this.log(null,'full');
		const log = $._log_echo ? $._log_echo(txt,this) : txt;
		console.log('\n'+log);  return this.#__logSave(txt);
	}
	// [内部]保持日志到文件
	#__logSave(txt){
		if(!(this.__srvHandle__.log??true)) return;
		let T=$.date(), [Y,M,D]=T.slice(0,10).split('-');
		txt = txt.replace(/\x1B\[(1;)?\d+m/g, '');
		if($._log_save) txt = $._log_save(txt, this);
		txt+='\n\n';  const DIR=$.TEMP+`/log/${Y}-`+M;
		const $PFS = require('node:fs').promises;
		const w = $PFS.mkdir(DIR, {recursive:true});
		const opx = {encoding:'utf8',flag:'a+',flush:true};
		w.then(()=>$PFS.writeFile(DIR+`/${D}.log`,txt,opx))
			.catch(e=>$.err('$.srvHandle.__logSave',e));
	}
};
